﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;

namespace LinqToPi
{
    public enum PiTimeFormat
    {
        Relative,
        Absolute,
        RelativeAbsolute,
        Unknown
    }

    [DataContract]
    public struct PiDateTime : IConvertible
    {
        #region Member Properties
        string piTime_backing;
        DateTime timestamp_backing;

        [DataMember]
        public DateTime TimeStamp
        {
            get
            {
                return timestamp_backing;
            }
            set
            {
                timestamp_backing = value;
            }
        }

        [DataMember]
        public string PiTime
        {
            get
            {
                return piTime_backing;
            }
            set
            {
                piTime_backing = value;
            }
        }
        #endregion

        #region Static Properties

        public static PiDateTime Now
        {
            get
            {
                return new PiDateTime(DateTime.Now);
            }
        }

        public static PiDateTime Today
        {
            get
            {
                return new PiDateTime(DateTime.Today);
            }
        }

        #endregion

        #region Static methods

        public bool Between(PiDateTime d1, PiDateTime d2)
        {
            return TimeStamp > d1 && TimeStamp < d2;
        }

        #endregion

        #region Constructors

        public PiDateTime(string piTime, PiTimeFormat format)
        {
            piTime_backing = piTime;
            timestamp_backing = Parse(piTime, format);
        }

        public PiDateTime(DateTime timestamp)
        {
            timestamp_backing = timestamp;
            piTime_backing = Parse(timestamp);
        }

        #endregion

        #region Operator overloads

        #region Casting
        public static implicit operator PiDateTime(DateTime timestamp)
        {
            return new PiDateTime(timestamp);
        }

        public static implicit operator PiDateTime(string piTime)
        {
            return new PiDateTime(piTime, PiTimeFormat.Relative);
        }

        
        public static explicit operator DateTime(PiDateTime piDateTime)
        {
            return piDateTime.TimeStamp;
        }

        public static explicit operator string(PiDateTime piDateTime)
        {
            return piDateTime.PiTime;
        }


        #endregion

        #region ==

        public static bool operator ==(PiDateTime time1, PiDateTime time2)
        {
            return time1.TimeStamp == time2.TimeStamp;
        }

        public static bool operator ==(PiDateTime time1, DateTime time2)
        {
            return time1.TimeStamp == time2;
        }

        public static bool operator ==(DateTime time1, PiDateTime time2)
        {
            return time1 == time2.TimeStamp;
        }

      

        #endregion

        #region !=

        public static bool operator !=(PiDateTime time1, PiDateTime time2)
        {
            return time1.TimeStamp != time2.TimeStamp;
        }

        public static bool operator !=(PiDateTime time1, DateTime time2)
        {
            return time1.TimeStamp != time2;
        }

        public static bool operator !=(DateTime time1, PiDateTime time2)
        {
            return time1 != time2.TimeStamp;
        }

        #endregion

        #region >

        public static bool operator >(PiDateTime time1, PiDateTime time2)
        {
            return time1.TimeStamp > time2.TimeStamp;
        }

        public static bool operator >(PiDateTime time1, DateTime time2)
        {
            return time1.TimeStamp > time2;
        }

        public static bool operator >(DateTime time1, PiDateTime time2)
        {
            return time1 > time2.TimeStamp;
        }

        #endregion

        #region <

        public static bool operator <(PiDateTime time1, PiDateTime time2)
        {
            return time1.TimeStamp < time2.TimeStamp;
        }

        public static bool operator <(PiDateTime time1, DateTime time2)
        {
            return time1.TimeStamp < time2;
        }

        public static bool operator <(DateTime time1, PiDateTime time2)
        {
            return time1 < time2.TimeStamp;
        }

        #endregion

        #region -

        public static TimeSpan operator -(PiDateTime time1, PiDateTime time2)
        {
            return time1.TimeStamp - time2.TimeStamp;
        }

        public static TimeSpan operator -(PiDateTime time1, DateTime time2)
        {
            return time1.TimeStamp - time2;
        }

        public static TimeSpan operator -(DateTime time1, PiDateTime time2)
        {
            return time1 - time2.TimeStamp;
        }


        #endregion






        #endregion

        #region Overrides
        public override int GetHashCode()
        {
            return TimeStamp.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            try
            {
                return (this == (PiDateTime)obj);
            }
            catch
            {
                return false;
            }
        }

        public override string ToString()
        {
            return PiTime;
        }

        #endregion

        #region Converting

        private void ConvertFromPiTime(string piTime, PiTimeFormat format)
        {
           
            switch (format)
            {
                case PiTimeFormat.Relative:
                    {

                        break;
                    }
                case PiTimeFormat.Absolute:
                    break;
                case PiTimeFormat.RelativeAbsolute:
                    break;
                case PiTimeFormat.Unknown:
                    break;
                default:
                    break;
            }
        }



        static Regex relativeRE = new Regex(@"( \s+ | \* | \d{2} | \d{2}\-\d{2}\-\d{2} | \d{1}\: | t | y | sa|su|mo|tu|w|th|f | [\+-]\d+[dhms] )", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
        static Regex timeOffsetRe = new Regex(@"([\+-]\d+)([dhms])", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);

        private static string Parse(DateTime time)
        {
            return time.ToString("dd-MMM-yy hh:mm:ss");
        }

        private static DateTime Parse(string piTime, PiTimeFormat format)
        {
            DateTime time = default(DateTime);

            switch (format)
            {
                case PiTimeFormat.Relative:
                    return time = ParseRelativePiTime(piTime, time);
                case PiTimeFormat.Absolute:
                    return DateTime.Parse(piTime);
                case PiTimeFormat.RelativeAbsolute:
                    throw new NotImplementedException("RelativeAbsolute not implemented");
                case PiTimeFormat.Unknown:
                    throw new NotImplementedException("Unkown not implemented");
                default:
                    break;
            }

            
            return time;
        }

        private static DateTime ParseRelativePiTime(string piTime, DateTime time)
        {
            foreach (Match m in relativeRE.Matches(piTime))
            {
                string val = m.Groups[1].Value;
                //this is a time representation of *, t, or y

                if (val == "*")
                {
                    time = DateTime.Now;
                    continue;
                }
                if (val == "t")
                {
                    time = DateTime.Today;
                    continue;
                }
                if (val == "y")
                {
                    time = DateTime.Today.AddDays(-1);
                    continue;
                }

                Match timeOffset = timeOffsetRe.Match(val);
                if (timeOffset.Success)
                {
                    double offset = double.Parse(timeOffset.Groups[1].Value.Trim('+'));
                    switch (timeOffset.Groups[2].Value)
                    {
                        case "d": time = time.AddDays(offset); break;
                        case "h": time = time.AddHours(offset); break;
                        case "m": time = time.AddMinutes(offset); break;
                        case "s": time = time.AddSeconds(offset); break;
                    }
                }
            }
            return time;
        }

        #endregion



        #region IConvertible Members

        public TypeCode GetTypeCode()
        {
            throw new NotImplementedException();
        }

        public bool ToBoolean(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public byte ToByte(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public char ToChar(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public DateTime ToDateTime(IFormatProvider provider)
        {
            return TimeStamp;
        }

        public decimal ToDecimal(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public double ToDouble(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public short ToInt16(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public int ToInt32(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public long ToInt64(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public sbyte ToSByte(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public float ToSingle(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public string ToString(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public object ToType(Type conversionType, IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public ushort ToUInt16(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public uint ToUInt32(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public ulong ToUInt64(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
